#include "Url.h"
#include "Containers/String.h"

#include <memory>

class CUrlToken
{
public:    
    enum class EUrlTokenType
    {
        PlainString,
        AfterProtocol, // "://"
        Slash, // "/"
        QuestionMark, // "?"
        Equal, // "="
        And, // "&"
        Hash, // "#"
        Colon, // ":" 
    };

    EUrlTokenType Type;
    CString PlainString; // only for PlainString type
};

CUrl::CUrl()
{
}

CUrl::~CUrl()
{

}

CString CUrl::GetHost() const
{
    return Host;
}

CString CUrl::GetPath() const
{
    return Path;
}

uint16_t CUrl::GetPort() const
{
    return Port;
}

TOptional<CUrl> CUrl::Create(const CString& InUrl)
{
    CUrl Url;
    if(Url.Parse(InUrl))
    {
        return Url;
    }
    else
    {
        return TOptional<CUrl>();
    }
}

bool CUrl::Parse(const CString &InUrl)
{
    //parse into tokens
    TVector<std::shared_ptr<CUrlToken>> Tokens;
    CString CurrentPlainString;
    for(auto CharIndex=0;CharIndex<InUrl.GetLength();CharIndex++)
    {
        char32_t Char = InUrl[CharIndex];

        switch(Char)
        {
            case U'#':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }

                auto HashToken = std::make_shared<CUrlToken>();
                HashToken->Type = CUrlToken::EUrlTokenType::Hash;
                Tokens.Add(HashToken);
                break;
            }

            case U'?':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }

                auto QuestionMarkToken = std::make_shared<CUrlToken>();
                QuestionMarkToken->Type = CUrlToken::EUrlTokenType::QuestionMark;
                Tokens.Add(QuestionMarkToken);
                break;
            }

            case U'=':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }

                auto EqualToken = std::make_shared<CUrlToken>();
                EqualToken->Type = CUrlToken::EUrlTokenType::Equal;
                Tokens.Add(EqualToken);
                break;
            }

            case U'&':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }
                
                auto AndToken = std::make_shared<CUrlToken>();
                AndToken->Type = CUrlToken::EUrlTokenType::And;
                Tokens.Add(AndToken);
                break;
            }

            case U':':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }

                //check if it is a protocol end
                if(CharIndex+2<InUrl.GetLength()&&
                    InUrl[CharIndex+1]==U'/'&&
                    InUrl[CharIndex+2]==U'/')
                {
                    auto AfterProtocolToken = std::make_shared<CUrlToken>();
                    AfterProtocolToken->Type = CUrlToken::EUrlTokenType::AfterProtocol;
                    Tokens.Add(AfterProtocolToken);
                    CharIndex += 2;
                    break;
                }

                auto ColonToken = std::make_shared<CUrlToken>();
                ColonToken->Type = CUrlToken::EUrlTokenType::Colon;
                Tokens.Add(ColonToken);
                break;
            }

            case U'/':
            {
                if(CurrentPlainString.GetLength()>0)
                {
                    auto StringToken = std::make_shared<CUrlToken>();
                    StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
                    StringToken->PlainString = CurrentPlainString;
                    Tokens.Add(StringToken);
                    CurrentPlainString.Clear();
                }
                
                auto SlashToken = std::make_shared<CUrlToken>();
                SlashToken->Type = CUrlToken::EUrlTokenType::Slash;
                Tokens.Add(SlashToken);
                break;
            }

            default:
            {
                CurrentPlainString+=Char;
                break;
            }
        }
    }
    if(CurrentPlainString.GetLength()>0)
    {
        auto StringToken = std::make_shared<CUrlToken>();
        StringToken->Type = CUrlToken::EUrlTokenType::PlainString;
        StringToken->PlainString = CurrentPlainString;
        Tokens.Add(StringToken);
        CurrentPlainString.Clear();
    }

    uint32_t TokenIndex = 0;
    //try find protocol
    if(Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::PlainString
        && Tokens[TokenIndex+1]->Type==CUrlToken::EUrlTokenType::AfterProtocol
    )
    {
        Protocol = Tokens[TokenIndex]->PlainString;
        TokenIndex+=2;
    }
    else
    {
        //cant find protocol in url,use default 
        Protocol=U"http";
    }

    //try find host
    if(Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::PlainString)
    {
        Host = Tokens[TokenIndex]->PlainString;
        TokenIndex++;
    }
    else
    {
        //cant find host in url
        return false;
    }

    //try find port
    if((TokenIndex+1)<Tokens.Num()
        &&Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::Colon
        && Tokens[TokenIndex+1]->Type==CUrlToken::EUrlTokenType::PlainString
    )
    {
        Port = Tokens[TokenIndex+1]->PlainString.ToUInt64();
        TokenIndex +=2;
    }

    //try find path
    if(TokenIndex < Tokens.Num() 
        &&Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::Slash)
    {
        Path = U'/';
        TokenIndex++;
        while(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::Slash
            || Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::PlainString
        )
        {
            if(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::PlainString)
            {
                Path+=U'/';
            }
            else
            {
                Path+=Tokens[TokenIndex]->PlainString;
            }
            TokenIndex++;
        }
    }
    else
    {
        //cant find path in url, use root path
        Path = U'/';
        //TokenIndex++;       
    }

    //try find query
    if(TokenIndex<Tokens.Num()
        &&Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::QuestionMark)
    {
        TokenIndex++; //skip question mark

        //parse querys
        while(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::PlainString)
        {
            CQuery Query;
            Query.Key = Tokens[TokenIndex]->PlainString;
            TokenIndex++; //skip key

            if(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::Equal)
            {
                TokenIndex++; //skip equal
                if(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::PlainString)
                {
                    Query.Value = Tokens[TokenIndex]->PlainString;
                    TokenIndex++; //skip value
                }
                else
                {
                    //cant find value in url
                    return false;
                }
            }
            else
            {
                //cant find equal in url
                return false;
            }

            Querys.Add(Query);

            if(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::And)
            {
                TokenIndex++; //skip and
            }
            else
            {
                break;
            }
        }

    }

    //try find fragment
    if(TokenIndex<Tokens.Num()
        &&Tokens[TokenIndex]->Type==CUrlToken::EUrlTokenType::Hash)
    {
        TokenIndex++; //skip hash
        if(Tokens[TokenIndex]->Type == CUrlToken::EUrlTokenType::PlainString)
        {
            Fragment = Tokens[TokenIndex]->PlainString;
            TokenIndex++; //skip fragment
        }
        else
        {
            //cant find fragment in url
            return false;
        }
    }

    //check if all tokens are parsed
    if(TokenIndex!=Tokens.Num())
    {
        //cant parse all tokens
        return false;
    }

    return true;
}
